#include "PreGraphic.h"
#include "RenderSystem.h"
#include "Camera.h"
#include "Viewport.h"
#include "Material.h"

RenderSystem::RenderSystem(void):mDevice(0),mViewport(0)
{
}

RenderSystem::~RenderSystem(void)
{
}

bool RenderSystem::create(
						  HINSTANCE hInstance,
						  HWND hwnd,
						  int width, int height,
						  bool windowed,
						  D3DDEVTYPE deviceType)
{

	HRESULT hr = 0;

	// Step 1: Create the IDirect3D9 object.

	IDirect3D9* d3d9 = 0;
	d3d9 = Direct3DCreate9(D3D_SDK_VERSION);

	if( !d3d9 )
	{
		::MessageBox(0, "Direct3DCreate9() - FAILED", 0, 0);
		return false;
	}

	// Step 2: Check for hardware vp.

	D3DCAPS9 caps;
	d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, deviceType, &caps);

	int vp = 0;
	if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
		vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
	else
		vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

	// Step 3: Fill out the D3DPRESENT_PARAMETERS structure.

	D3DPRESENT_PARAMETERS d3dpp;
	d3dpp.BackBufferWidth            = width;
	d3dpp.BackBufferHeight           = height;
	d3dpp.BackBufferFormat           = D3DFMT_A8R8G8B8;
	d3dpp.BackBufferCount            = 1;
	d3dpp.MultiSampleType            = D3DMULTISAMPLE_NONE;
	d3dpp.MultiSampleQuality         = 0;
	d3dpp.SwapEffect                 = D3DSWAPEFFECT_DISCARD; 
	d3dpp.hDeviceWindow              = hwnd;
	d3dpp.Windowed                   = windowed;
	d3dpp.EnableAutoDepthStencil     = true; 
	d3dpp.AutoDepthStencilFormat     = D3DFMT_D24S8;
	d3dpp.Flags                      = 0;
	d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
	d3dpp.PresentationInterval       = D3DPRESENT_INTERVAL_IMMEDIATE;

	// Step 4: Create the device.

	hr = d3d9->CreateDevice(
		D3DADAPTER_DEFAULT, // primary adapter
		deviceType,         // device type
		hwnd,               // window associated with device
		vp,                 // vertex processing
		&d3dpp,             // present parameters
		&mDevice);            // return created device

	if( FAILED(hr) )
	{
		// try again using a 16-bit depth buffer
		d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

		hr = d3d9->CreateDevice(
			D3DADAPTER_DEFAULT,
			deviceType,
			hwnd,
			vp,
			&d3dpp,
			&mDevice);

		if( FAILED(hr) )
		{
			d3d9->Release(); // done with d3d9 object
			::MessageBox(0, "CreateDevice() - FAILED", 0, 0);
			return false;
		}
	}

	d3d9->Release(); // done with d3d9 object
	return true;
}

void RenderSystem::destroy()
{
	if(mDevice){
		mDevice->Release();
		mDevice = 0;
	}	
}

void RenderSystem::setWorldMatrix(const Matrix4& mat)
{
	mMaxWorld = mat;
	D3DXMATRIX d3dmax ;
	D3DXMatrixTranspose(&d3dmax,(const D3DXMATRIX*)(&mat));

	mDevice->SetTransform(D3DTS_WORLD,&d3dmax);
}

void RenderSystem::setViewMatrix(const Matrix4& mat)
{
	mMaxView = mat;
	D3DXMATRIX d3dmax ;
	D3DXMatrixTranspose(&d3dmax,(const D3DXMATRIX*)(&mat));

	mDevice->SetTransform(D3DTS_VIEW,&d3dmax);

}

void RenderSystem::setProjectionMatrix(const Matrix4& mat)
{
	mMaxProjection = mat;
	D3DXMATRIX d3dmax ;
	D3DXMatrixTranspose(&d3dmax,(const D3DXMATRIX*)(&mat));

	mDevice->SetTransform(D3DTS_PROJECTION,&d3dmax);
}

void RenderSystem::getProjectionMatrix(Matrix4 &mat, Camera *cam)
{
	Real nearPlane = cam->getNearPlane();
	Real farPlane = cam->getFarPlane();
	Radian fovY = cam->getFovY();
	Real aspect = cam->getAspect();

	Real nearH = 2*nearPlane * Math::Tan(fovY/2.f);
	Real nearW = aspect * nearH;

	if (cam->getProjectionType() == Camera::PT_Perspective) {
		D3DXMatrixPerspectiveRH((D3DXMATRIX*)(&mat), nearW, nearH, nearPlane, farPlane);
		mat.transpose();
	}
	else {
		// orthogonal projection.

		// bug ??
		//D3DXMatrixOrthoRH((D3DXMATRIX*)(&mat), cam->getWidth(), cam->getHeight(), nearPlane, farPlane);
		//mat.transpose();

		mat = Matrix4::ZERO;
		mat._11 = 2.f/cam->getWidth();
		mat._22 = 2.f/cam->getHeight();
		mat._33 = -1.f/(farPlane - nearPlane);
		mat._44 = 1;
		mat._34 = -nearPlane/(farPlane - nearPlane);
	}
}

void RenderSystem::setViewport(Viewport* vp)
{
	mViewport = vp;

	D3DVIEWPORT9 d3dvp;
	d3dvp.X = vp->getLeft();
	d3dvp.Y = vp->getBottom();
	d3dvp.Width = vp->getWidth();
	d3dvp.Height = vp->getHeight();
	d3dvp.MinZ = 0.f;
	d3dvp.MaxZ = 0.f;

	mDevice->SetViewport(&d3dvp);
}

void RenderSystem::setFillMode(RenderSystem::FillMode fm)
{
	mFillMode = fm;
	D3DFILLMODE d3dfm = D3DFILL_WIREFRAME;
	switch(fm){
		case FM_Point:     d3dfm = D3DFILL_POINT; break;
		case FM_Wireframe: d3dfm = D3DFILL_WIREFRAME; break;
		case FM_Solid:     d3dfm = D3DFILL_SOLID; break;
		default: assert(false);
	}
	mDevice->SetRenderState(D3DRS_FILLMODE,d3dfm);
}

RenderSystem::FillMode RenderSystem::getFillMode() const
{
	return mFillMode;
}

void RenderSystem::setCullMode(CullMode cm)
{
	mCullMode = cm;
	D3DCULL d3dcm = D3DCULL_NONE;
	switch(mCullMode){
		case CM_None: d3dcm = D3DCULL_NONE; break;
		case CM_Cw:		d3dcm = D3DCULL_CW; break;
		case CM_Ccw:	d3dcm = D3DCULL_CCW; break;
	}
	mDevice->SetRenderState(D3DRS_CULLMODE,d3dcm);
}

RenderSystem::CullMode RenderSystem::getCullMode() const
{
	return mCullMode;
}

void RenderSystem::enableLight(bool bEnable)
{
	mIsEnableLight = bEnable;
	mDevice->SetRenderState(D3DRS_LIGHTING,mIsEnableLight);
	mDevice->SetRenderState(D3DRS_SPECULARENABLE,mIsEnableLight);
}

bool RenderSystem::isEnableLight() const
{
	return mIsEnableLight;
}
